﻿using GameToolkit;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Devil.Persistent
{
    public delegate void PersistentDataAction(IPersistentMeta meta, bool success);

    public partial class PersistentSystem : IValidator
    {
        static readonly object _singletonLock = new object();
        static PersistentSystem _singleton;
        public static PersistentSystem Instance => _singleton;

        public static PersistentSystem Create(string pwd, int bufferSize = 512)
        {
            lock (_singletonLock)
            {
                if (_singleton == null || _singleton.mReleased)
                {
                    _singleton = new PersistentSystem(pwd, bufferSize);
                }
                return _singleton;
            }
        }

        public static void Destroy()
        {
            lock (_singletonLock)
            {
                if (_singleton != null && !_singleton.mReleased)
                {
                    _singleton.OnRelease();
                }
                _singleton = null;
            }
        }

        // 密钥
        readonly string mPwd;
        int mBufferSize;
        bool mReleased;
        // 数据模块
        HashSet<IPersistentModule> mModules;

        ParallelDispatcher.Handler mGameJob;
        ParallelDispatcher.Handler mSystemJob;

        public ParallelDispatcher.Handler GameJob => mGameJob;
        public ParallelDispatcher.Handler SystemJob => mSystemJob;
        public bool IsValid => !mReleased;

        private PersistentSystem(string pwd, int bufferSize = 512) 
        {
            mPwd = pwd;
            mBufferSize = bufferSize;
            mModules = new HashSet<IPersistentModule>();
        }

        ~PersistentSystem()
        {
            lock (_singletonLock)
            {
                if (!mReleased)
                {
                    OnRelease();
                }
            } 
        }

        private void OnRelease()
        {
            mReleased = true;
            mOnSaveBegin = null;
            mOnLoadBegin = null;
            mOnSaved = null;
            mOnLoaded = null;

            mSystemJob.Abort();
            mGameJob.Abort();
            mModules.Clear();
        }

        public void Regist([NotNull] IPersistentModule module)
        {
            lock (_singletonLock)
            {
                if (mReleased)
                    throw new System.InvalidOperationException("The persistent system was destroied.");
                if (mGameJob.IsAlive || mSystemJob.IsAlive)
                    throw new System.InvalidOperationException($"Can't regist persistent module during saving or loading game datas.");
                mModules.Add(module);
            }
        }

        public void Unregist([NotNull] IPersistentModule module)
        {
            lock(_singletonLock)
            {
                if (mReleased)
                    throw new System.InvalidOperationException("The persistent system was destroied.");
                if (mGameJob.IsAlive || mSystemJob.IsAlive)
                    throw new System.InvalidOperationException($"Can't unregist persistent module during saving or loading game datas.");
                mModules.Remove(module);
            }
        }

        public T GetModule<T>()
        {
            lock(_singletonLock)
            {
                if (mReleased)
                    throw new System.InvalidOperationException("The persistent system was destroied.");
                foreach (var mod in mModules)
                {
                    if (mod is T t)
                        return t;
                }
            }
            return default;
        }

        public ParallelDispatcher.Handler Save(IPersistentMeta meta, string filePath)
        {
            ParallelDispatcher.Handler job;
            System.Action<IPersistentMeta> callback;
            lock (_singletonLock)
            {
                if (mReleased)
                    throw new System.InvalidOperationException("The persistent system was destroied.");
                if (!ParallelUnity.IsMainThread)
                    throw new System.InvalidOperationException("The saving command can only be invoked form main thread.");
                if (meta == null || meta.BlockType == EPersistentType.System)
                {
                    if (mSystemJob.IsAlive)
                        throw new System.InvalidOperationException("The saving or loading system command was already started.");
                    if (meta != null)
                        meta.OnWillSaveFile(filePath);
                    var cmd = new SaveGameCmd(this, meta, filePath);
                    mSystemJob = cmd.Schedule(false);
                    mSystemJob.SampleExecuteTime("Persistent");
                    job = mSystemJob;
                }
                else
                {
                    if (mGameJob.IsAlive)
                        throw new System.InvalidOperationException("The saving or loading game command was already started.");
                    meta.OnWillSaveFile(filePath);
                    var cmd = new SaveGameCmd(this, meta, filePath);
                    mGameJob = cmd.Schedule(false);
                    mGameJob.SampleExecuteTime("Persistent");
                    job = mGameJob;
                }
                callback = mOnSaveBegin;
            }
            callback?.Invoke(meta);
            return job;
        }

        public ParallelDispatcher.Handler Read(IPersistentMeta meta, string filePath)
        {
            ParallelDispatcher.Handler job;
            System.Action<IPersistentMeta> callback;
            lock (_singletonLock)
            {
                if (mReleased)
                    throw new System.InvalidOperationException("The persistent system was destroied.");
                if (!ParallelUnity.IsMainThread)
                    throw new System.InvalidOperationException("The loading command can only be invoked form main thread.");
                if (meta == null || meta.BlockType == EPersistentType.System)
                {
                    if (mSystemJob.IsAlive)
                        throw new System.InvalidOperationException("The saving or loading system command was already started.");
                    var cmd = new ReadGameCmd(this, meta, filePath);
                    mSystemJob = cmd.Schedule(false);
                    mSystemJob.SampleExecuteTime("Persistent");
                    job = mSystemJob;
                }
                else
                {
                    if (mGameJob.IsAlive)
                        throw new System.InvalidOperationException("The saving or loading game command was already started.");
                    var cmd = new ReadGameCmd(this, meta, filePath);
                    mGameJob = cmd.Schedule(false);
                    mGameJob.SampleExecuteTime("Persistent");
                    job = mGameJob;
                }
                callback = mOnLoadBegin;
            }
            callback?.Invoke(meta);
            return job;
        }
    }
}
